<?php

namespace Modules\DataCheck\Services;

use Illuminate\Support\Facades\Log;
use Modules\Common\Support\Helper;
use Modules\DataCheck\Enums\DataCheckMethodEnum;
use Modules\DataCheck\Enums\ErrorEnum;
use Modules\DataCheck\Enums\RunModeEnum;
use Modules\DataCheck\Enums\TaskStateEnum;
use Modules\DataCheck\Excel\Export\DcTaskExport;
use Modules\DataCheck\Excel\Import\TargetDataRead;
use Modules\DataCheck\Excel\Tratis\Excel;
use Modules\DataCheck\Exceptions\DataCheckException;
use Modules\DataCheck\Models\DcTask;
use Modules\DataCheck\Models\DcTaskSourceData;
use Modules\DataCheck\Models\DcTaskSourceFile;
use Modules\DataCheck\Models\DcTaskTargetResult;
use Pro\Support\IntervalChecker;

/**
 * 数据核对任务
 */
class DataCheckService
{
    /**
     * @var string
     */
    public static string $taskProcessKeyPrefix = 'data_check_task_process_';

    private string $realTargetFile = '';

    /**
     * 运行模式
     * @var RunModeEnum
     */
    private RunModeEnum $runModel;

    /**
     * 读取 excel 后进度
     * @var int
     */
    private int $readExcelProcess = 5;

    /**
     * 跳过行数
     * @var int
     */
    private int $skipLine = 1;

    /**
     * 字段映射
     * @var array
     */
    private array $fieldMap = [
        1 => 'department', //部门
        2 => 'org', //领用人/机构
        3 => 'code', //单证编号
        4 => 'name',  //单证名称,
        5 => 'serial_num_start', //开始流水号
        6 => 'serial_num_end', //结束流水号
        7 => 'num', //数量
    ];

    use Excel;

    /**
     * 核对完成进度
     * @var int
     */
    private int $checkCompleteProcess = 95;

    public function __construct(private readonly TaskProcessService $processService)
    {
    }

    /**
     * 数据核对
     * @param DcTask      $task
     * @param RunModeEnum $runModel
     * @return void
     */
    public function handle(DcTask $task, RunModeEnum $runModel): void
    {
        try {
            //获取最新的任务 防止状态发生变化
            $latestTask = DcTask::query()->where('task_id', $task->task_id)->first();

            if (empty($latestTask)) {
                throw new DataCheckException('核对任务不存在', ErrorEnum::DATA_CHECK);
            }

            // 状态判断
            if ($latestTask->task_state != TaskStateEnum::WAIT_RUNNING->value) {
                return;
                //throw new DataCheckException('核对任务已执行，不可重新执行', ErrorEnum::DATA_CHECK);
            }

            //获取真实文件路径
            $targetFile = $latestTask->target_file['file_path'] ?? '';
            $this->realTargetFile = Helper::getUploadReaPath($targetFile);

            // 判断待核对文件是否存在
            if (!file_exists($this->realTargetFile)) {
                throw new DataCheckException('核对表文件不存在', ErrorEnum::DATA_CHECK);
            }

            $sourceFile = DcTaskSourceFile::query()->find($latestTask->source_file_id);
            if (empty($sourceFile)) {
                throw new DataCheckException('原表数据不存在', ErrorEnum::DATA_CHECK);
            }

            $this->runModel = $runModel;

            $this->run($task);
        } catch (\Throwable $throwable) {
            $errorMsg = '数据核对异常';
            if ($throwable instanceof DataCheckException) {
                $errorMsg = $throwable->getMessage();
            }

            //更新为导入失败状态
            $update = [
                'updated_at' => time(),
                'task_remark' => json_encode([
                    'msg' => $errorMsg,
                    'error' => [
                        'msg' => $throwable->getMessage(),
                        'file' => $throwable->getFile(),
                        'line' => $throwable->getLine(),
                    ],
                ], JSON_UNESCAPED_UNICODE),
                'task_state' => TaskStateEnum::FAILED->value,
            ];

            DcTask::query()->where('task_id', $task->task_id)->update($update);

            //记录日志
            $log = [
                'msg' => $errorMsg,
                'task_id' => $task->task_id,
                'real_target_file' => $this->realTargetFile,
                'target_file' => json_encode($task->target_file, JSON_UNESCAPED_UNICODE),
                'error' => [
                    'msg' => $throwable->getMessage(),
                    'file' => $throwable->getFile(),
                    'line' => $throwable->getLine(),
                ],
            ];
            Log::error(json_encode($log, JSON_UNESCAPED_UNICODE));
        } finally {
            if ($runModel->equal(RunModeEnum::JOB)) {
                //删除进度
                $this->processService->delete(self::getTaskProcessKey($task->task_id));
            }

            //删除任务停止标识
            TaskStopService::clear($task->task_id);
        }
    }

    /**
     * 进度说明
     *     1 excel 读取 10
     *     2 数据核对 11-75
     *     3 数据导出 excel 写入 76-99
     *
     * @param DcTask      $task
     * @return void
     */
    private function run(DcTask $task): void
    {
        echo "开始处理任务：{$task->task_id}\n";

        //更新任务状态为执行中
        $res = DcTask::query()->where(['task_id' => $task->task_id])->update([
            'task_state' => TaskStateEnum::RUNNING->value,
            'updated_at' => time(),
            'task_remark' => '',
        ]);
        if (!$res) {
            throw new DataCheckException('更新核对任务执行状态失败', ErrorEnum::DATA_CHECK);
        }

        //删除原来核对的数据
        DcTaskTargetResult::query()
            ->where('task_id', $task->task_id)
            ->delete();

        //只有队列才会记录进度
        if ($this->runModel->equal(RunModeEnum::JOB)) {
            $this->processService->init(self::getTaskProcessKey($task->task_id));
        }

        //获取总行数
        $targetDataRead = new TargetDataRead();
        $excelTotalCount = $targetDataRead->getFileLineCount($this->realTargetFile, $this->skipLine);

        echo "总行数：{$excelTotalCount}\n";

        //读取excel完成进度
        $this->setTaskProcess($this->readExcelProcess);

        //数据核对进度区间范围
        $checkProcessRange = $this->checkCompleteProcess - $this->readExcelProcess;

        $fileId = $task->source_file_id;
        $resultData = [];
        $totalLine = 0; //总行数
        $totalCount = 0; //总数量

        $pathInfo = pathinfo($this->realTargetFile);
        $reader  = new \Vtiful\Kernel\Excel(['path' => $pathInfo['dirname']]);
        $sheetList = $reader->openFile($pathInfo['basename'])->sheetList();

        //判断任务是否已停止
        if ($this->taskIsStop($task->task_id)) {
            echo "任务 {$task->task_id} 已停止 \n";
            return;
        }

        foreach ($sheetList as $sheetName) {
            echo "开始处理表：{$sheetName}...\n";

            $reader->openSheet($sheetName)->setSkipRows($this->skipLine);

            $line = $this->skipLine + 1;
            while (($row = $reader->nextRow()) !== null && array_filter($row) !== []) {
                $item = $this->transDataT($this->fieldMap, $row, [], false, $line, $sheetName);

                if (empty($item['serial_num_start'])) {
                    throw new DataCheckException("{$sheetName} 第{$line}行开始流水号为空", ErrorEnum::DATA_CHECK);
                }

                if (empty($item['serial_num_end'])) {
                    throw new DataCheckException("{$sheetName} 第{$line}行结束流水号为空", ErrorEnum::DATA_CHECK);
                }

                $item['serial_num_start'] = intval($item['serial_num_start']);
                $item['serial_num_end'] = intval($item['serial_num_end']);

                //获取区间 [serial_num_start, serial_num_end] 在总表种重叠的区间
                $overlapping = $this->findOverlappingIntervals($fileId, $item['serial_num_start'], $item['serial_num_end']);

                //检查区间[serial_num_start, serial_num_end]在表中的存在情况
                $result = IntervalChecker::checkInterval($overlapping, $item['serial_num_start'], $item['serial_num_end']);

                foreach ($result as $row) {
                    $resultData[] = [
                        'task_id' => $task->task_id,
                        'source_file_id' => $fileId,
                        'sheet_name' => $sheetName,
                        'serial_num_start' => $row['start'],
                        'serial_num_end' => $row['end'],
                        'line_data' => json_encode($item, JSON_UNESCAPED_UNICODE),
                        'line_num' => $line,
                        'check_state' => $row['check_state'],
                        'created_at' => time(),
                    ];

                    $totalCount++;
                }
                $line++;

                $totalLine++;

                // 批量插入
                if (count($resultData) >= 20) {
                    DcTaskTargetResult::insert($resultData);
                    $resultData = [];

                    //设置进度
                    $processIncr = intval(($totalLine / $excelTotalCount) * $checkProcessRange);
                    $this->setTaskProcess($this->readExcelProcess + $processIncr);

                    //判断任务是否已停止
                    if ($this->taskIsStop($task->task_id)) {
                        echo "任务 {$task->task_id} 已停止 \n";
                        return;
                    }
                }

                if ($totalLine % 100 == 0 || $totalCount % 100 == 0) {
                    echo "已处理行数：{$totalLine}，已处理数量：{$totalCount}\n";
                }
            }

            echo "表处理完成：{$sheetName}\n";
        }

        // 如果剩余数据，则插入
        if (!empty($resultData)) {
            DcTaskTargetResult::insert($resultData);
        }
        unset($resultData);

        //判断任务是否已停止
        if ($this->taskIsStop($task->task_id)) {
            echo "任务 {$task->task_id} 已停止 \n";
            return;
        }

        //核对完成进度
        $this->setTaskProcess($this->checkCompleteProcess);

        echo "任务处理完成：{$task->task_id}, 总行数：{$totalLine}, 总数量：{$totalCount}\n\n";

        //导出excel
        echo "开始导出结果到excel...\n";
        $dcTaskExport = new DcTaskExport();
        $exportResult = $dcTaskExport->handle(
            $task,
            $this->runModel->equal(RunModeEnum::JOB) ? $this->processService : null,
            $this->checkCompleteProcess,
            99
        );
        echo "导出到excel完成：{$exportResult['url']}\n";

        //更新任务状态
        $update = [
            'updated_at' => time(),
            'task_remark' => '',
            'file_download_url' => $exportResult['url'],
            'task_state' => TaskStateEnum::COMPLETE->value,
        ];

        //查询最新状态，如果是停止，更新状态为核对已停止(核对完成)
        /*$newTask = DcTask::query()->where('task_id', $task->task_id)->first();
        if ($newTask->task_state == TaskStateEnum::STOP->value) {
            $update['task_state'] = TaskStateEnum::STOP_CHECK_COMPLETE->value;
        }*/
        $task->update($update);

        //判断任务是否已停止
        if ($this->taskIsStop($task->task_id)) {
            echo "任务 {$task->task_id} 已停止 \n";
            return;
        }

        //设置完成进度
        $this->setTaskProcess(100);
    }

    /**
     * 查询区间 [a, b] 有重叠的区间
     * @param int $fileId
     * @param int $start
     * @param int $end
     * @return array
     */
    private function findOverlappingIntervals(int $fileId, int $start, int $end): array
    {
        /*
            SELECT serial_num_start, serial_num_end, check_state
            FROM {$this->table}
            WHERE serial_num_start <= :b AND serial_num_end >= :a
            ORDER BY serial_num_start
         */
        $field = [
            'serial_num_start',
            'serial_num_end',
            'check_state',
        ];
        return DcTaskSourceData::query()->select($field)
            ->where('file_id', $fileId)
            ->where('serial_num_start', '<=', $end)
            ->where('serial_num_end', '>=', $start)
            ->orderBy('serial_num_start', 'asc')->get()->toArray();
    }

    /**
     * 获取任务进度缓存key
     * @param string $taskId
     * @return string
     */
    public static function getTaskProcessKey(string $taskId): string
    {
        return self::$taskProcessKeyPrefix . $taskId;
    }

    /**
     * 设置任务进度  只有队列执行的才会设置进度
     * @param int $process
     * @return void
     */
    private function setTaskProcess(int $process): void
    {
        if ($this->runModel->equal(RunModeEnum::JOB)) {
            $this->processService->set($process);
        }
    }

    /**
     * 检查任务是否停止
     * @param string $taskId
     * @return bool
     */
    private function taskIsStop(string $taskId): bool
    {
        if (TaskStopService::isStop($taskId)) {
            DcTask::query()->where(['task_id' => $taskId])->update([
                'task_state' => TaskStateEnum::STOP->value,
                'updated_at' => time(),
                'task_remark' => '',
            ]);
            TaskStopService::clear($taskId); //清理停止任务标识
            return true;
        } else {
            return false;
        }
    }
}